<?php
	// 开启严格模式
	declare(strict_types=1);
	// 不限制运行时间
	set_time_limit(0);
	// 仅限CLI模式下运行
	if(php_sapi_name() !== 'cli')
	{
		echo '仅支持在CLI模式下运行！' . PHP_EOL;
		exit;
	}

	// 全局参数定义
	$db_host = $db_user = $db_password = $db_name = $db_charset = $remark = $sys_table_prefix = $app_table_prefix = '';
	$db_port = 0;
	$sys_table_whitelist = [];


	/**
	 * 加载配置文件
	 */
	function loadConfig()
	{
		global $db_host, $db_user, $db_password, $db_name, $db_charset, $remark, $sys_table_prefix, $app_table_prefix, $db_port, $sys_table_whitelist;

		echo PHP_EOL . '加载配置文件...' . PHP_EOL;
		$conf_file = dirname(__FILE__) . '/backup_conf.php';
		if( !file_exists($conf_file) )
		{
			echo PHP_EOL . "配置文件丢失（{$conf_file}），退出备份" . PHP_EOL;
			exit;
		}
		$backup_conf = require_once($conf_file);
		if(!$backup_conf)
		{
			echo PHP_EOL . "配置文件未包含有效内容，退出备份" . PHP_EOL;
			exit;
		}

		// 解析数据库配置参数
		$db_host = $backup_conf['db_conf']['host'] ?? null;
		$db_user = $backup_conf['db_conf']['user'] ?? null;
		$db_password = $backup_conf['db_conf']['password'] ?? null;
		$db_name = $backup_conf['db_conf']['db'] ?? null;
		$db_port = $backup_conf['db_conf']['port'] ?? null;
		$db_charset = $backup_conf['db_conf']['charset'] ?? null;
		$remark = $backup_conf['remark'] ?? null;
		if( is_null($db_host) || is_null($db_port) || is_null($db_user) || is_null($db_password) || is_null($db_name) )
		{
			echo '解析数据库配置参数失败，退出备份' . PHP_EOL;
			exit;
		}
		// 解析系统表配置
		$sys_table_prefix = $backup_conf['sys_table']['prefix'] ?? null;
		$sys_table_whitelist = $backup_conf['sys_table']['whitelist'] ?? null;
		if( is_null($sys_table_prefix) || is_null($sys_table_whitelist) )
		{
			echo '解析系统表配置参数失败，退出备份' . PHP_EOL;
			exit;
		}
		// 解析应用表配置
		$app_table_prefix = $backup_conf['app_table']['prefix'] ?? null;
		if( is_null($app_table_prefix) )
		{
			echo '解析应用表配置参数失败，退出备份' . PHP_EOL;
			exit;
		}
		// 强制类型转换
		$db_host = (string)$db_host;
		$db_user = (string)$db_user;
		$db_password = (string)$db_password;
		$db_name = (string)$db_name;
		$db_port = (int)$db_port;
		$db_charset = (string)$db_charset;
		$remark = (string)$remark;
		$sys_table_prefix = (string)$sys_table_prefix;
		$app_table_prefix = (string)$app_table_prefix;
		$sys_table_whitelist = (array)$sys_table_whitelist;
		// 系统表白名单去重
		$sys_table_whitelist = array_unique($sys_table_whitelist);
		echo '配置文件加载完成' . PHP_EOL;
	}


	/**
	 * 获取数据表列表
	 * @return Array $table_list
	 */
	function getTableList() : Array
	{
		global $db_host, $db_user, $db_password, $db_name, $db_port, $db_charset;

		// 连接数据库
		echo PHP_EOL . '连接数据库...' . PHP_EOL;
		$con = mysqli_connect($db_host, $db_user, $db_password, $db_name, $db_port);
		if(!$con)
		{
			echo '连接数据库失败，退出备份' . PHP_EOL;
		}
		echo '数据库连接成功' . PHP_EOL;
		// 设置字符集
		mysqli_set_charset($con, $db_charset);
		// 获取库中的表
		echo '遍历数据库...' . PHP_EOL;
		$res = mysqli_query($con, 'SHOW TABLES;');
		if(!$res)
		{
			echo '数据库遍历失败，退出备份' . PHP_EOL;
			exit;
		}
		$table_list = [];
		while($row = mysqli_fetch_row($res) )
		{
			$table_list[] = $row[0];
		}
		mysqli_close($con);

		return $table_list;
	}


	/**
	 * 按条件对列表分组
	 * @param Array $table_list 待分组数据
	 * @param Mixed $pattern 分组条件
	 * @param Array $res 储存结果的数组
	 */
	function listGrouping(array &$table_list, $pattern, array &$res)
	{
		// $pattert为字符串，按正则模式匹配
		if( is_string($pattern) )
		{
			foreach($table_list as $key=>$val)
			{
				if( preg_match($pattern, $val) )
				{
					$res[] = $val;
					// 删除已匹配的表，优化性能
					unset($table_list[$key]);
				}
			}
		}
		// $pattert为数组，用array_search()匹配
		if( is_array($pattern) )
		{
			foreach($pattern as $key=>$val)
			{
				if( $key = array_search($val, $table_list) )
				{
					$res[] = $val;
					// 删除已匹配的表，优化性能
					unset($table_list[$key]);
				}
			}
		}
	}


	/**
	 * 生成mysqldump配置文件
	 */
	function createMysqldumpConfFile()
	{
		global $db_user, $db_password;

		$contents = [
			'[mysqldump]',
			"user = {$db_user}",
			"password = {$db_password}"
		];
		foreach($contents as $key=>$val)
		{
			$text = $val . PHP_EOL;
			file_put_contents('mysqldump.cnf', $text, FILE_APPEND);
		}
	}


	/**
	 * 执行导出
	 * @param String $filename 导出文件名
	 * @param String $type 导出类型 [db-仅导出库（不含表） sys-仅导出系统表 app-仅导出应用表 all-全量导出]
	 * @param String $table_name 表名，$type=sys/app时必填
	 */
	function executeExport(string $filename, string $type, string $table_name='')
	{
		global $db_host, $db_port, $db_user, $db_name;

		// 导出命令模板
		$cmd_tpl = "mysqldump --defaults-file=mysqldump.cnf -h {$db_host} -P {$db_port} ";
		// 命令列表
		$cmd_list = [];

		/**
		 * 参数说明：
		 * -B：指定数据库
	 	 * -t：不导出表结构
		 * -c：包含完整列名，提高导入效率
	 	 * -d：不导出表数据
		 * -e：聚合多组value，压缩文件体积
		 * --add-drop-database：建库前先删除
		 * --skip-comments：省略mysqldump的注释信息
		 */
		switch($type)
		{
			case 'db':
				$cmd_list[] = "{$cmd_tpl} --add-drop-database -t -d -B {$db_name} > {$filename}";
				break;
			case 'sys':
				$cmd_list[] = "{$cmd_tpl} --skip-comments -c -e {$db_name} {$table_name} >> {$filename}";
				break;
			case 'app':
				$cmd_list[] = "{$cmd_tpl} --skip-comments -d {$db_name} {$table_name} >> {$filename}";
				// 追加截断表语句，清除表结构中的自增属性
				$cmd_list[] = "echo TRUNCATE TABLE `{$table_name}`; >> {$filename} ";
				break;
			case 'all':
				$cmd_list[] = "{$cmd_tpl} -c -e -B {$db_name} > {$filename}";
				break;
			default:
				echo "内部错误：未知的导出类型" . PHP_EOL;
				exit;
		}
		// 执行命令列表
		foreach($cmd_list as $key=>$val)
		{
			exec($val);
		}
	}


	/**
	 * 清理临时文件
	 */
	function clearTmpFile()
	{
		$tmp_file_list = [
			'mysqldump.cnf'
		];
		foreach($tmp_file_list as $key=>$val)
		{
			unlink($val);
		}
	}


	/**
	 * 初始化备份
	 */
	function _INIT()
	{
		global $db_name, $sys_table_prefix, $app_table_prefix, $sys_table_whitelist;

		$sys_table_list = $app_table_list = [];
		// 获取数据表列表
		$table_list = getTableList();
		// 统计表的总数
		$total_table_amount = count($table_list);
		// 获取系统表列表及数量
		listGrouping($table_list, "/^{$sys_table_prefix}/", $sys_table_list);
		listGrouping($table_list, $sys_table_whitelist, $sys_table_list);
		$sys_table_amount = count($sys_table_list);
		// 获取应用表列表及数量
		listGrouping($table_list, "/^{$app_table_prefix}/", $app_table_list);
		$app_table_amount = count($app_table_list);
		// 统计未匹配表的数量
		$unmatch_table_amount = count($table_list);

		// 确认信息
		$confirm_info = PHP_EOL . "共找到{$total_table_amount}张表，系统表{$sys_table_amount}张，应用表{$app_table_amount}张";
		if($unmatch_table_amount)
		{
			$confirm_info .= "，未匹配表{$unmatch_table_amount}张（将被舍弃）";
			// 输出未匹配表
			$unmatch_count = 0;
			$unmatch_tables = PHP_EOL . PHP_EOL . "\033[0;31m未匹配表：" . PHP_EOL;
			foreach($table_list as $key=>$val)
			{
				$unmatch_tables .= "{$val}\t";
				$unmatch_count++;
				if( $unmatch_count % 3 == 0 )
				{
					echo PHP_EOL;
				}
			}
			$unmatch_tables .= "\033[0;37m" . PHP_EOL;
			$confirm_info .= $unmatch_tables;
		}
		// 输出确认信息
		echo $confirm_info;

		// 输入错误次数（最大3次）
		$err_time = 0;
	CONFIRM_EXPORT:
		echo PHP_EOL . '是否备份？(y/n)' . PHP_EOL;
		// 交互，获取用户输入（确认是否导出）
		$confirm_export = fgets(STDIN);
		$confirm_export = trim( strtolower($confirm_export) );
		switch($confirm_export)
		{
			case 'y':
				break;
			case 'n':
				echo PHP_EOL . '退出备份' . PHP_EOL;
				break;
			default:
				if($err_time < 2)
				{
					echo PHP_EOL . '输入错误，请重新选择：';
					$err_time++;
					goto CONFIRM_EXPORT;
				}
				else
				{
					echo PHP_EOL . '错误次数过多，退出备份' . PHP_EOL;
					exit;
				}
		}

		// 备份文件名
		$datetime = date('YmdHis');
		$sql_filename = "{$db_name}_INIT_{$datetime}.sql";
		// 导出库
		echo PHP_EOL . '备份数据库...' . PHP_EOL;
		executeExport($sql_filename, 'db');
		// 导出系统表
		foreach($sys_table_list as $key=>$val)
		{
			echo "备份{$val}表..." . PHP_EOL;
			executeExport($sql_filename, 'sys', $val);
		}
		// 导出应用表
		foreach($app_table_list as $key=>$val)
		{
			echo "备份{$val}表..." . PHP_EOL;
			executeExport($sql_filename, 'app', $val);
		}

		echo PHP_EOL . '备份完成' . PHP_EOL;
	}


	/**
	 * 全量备份
	 */
	function _BACKUP()
	{
		global $db_name;

		// 备份文件名
		$datetime = date('YmdHis');
		$sql_filename = "{$db_name}_BACKUP_{$datetime}.sql";

		echo PHP_EOL . '执行全量备份...' . PHP_EOL;
		executeExport($sql_filename, 'all');
		echo '全量备份完成' . PHP_EOL;
	}




	// 加载配置文件
	loadConfig();
	// 生成mysqldump配置文件（临时）
	createMysqldumpConfFile();
	// 输出配置摘要
	echo PHP_EOL . '=================================' . PHP_EOL;
	echo "数据库地址：{$db_host}:{$db_port}" . PHP_EOL . "数据库名：{$db_name}";
	echo PHP_EOL . '=================================' . PHP_EOL;
	echo PHP_EOL . "正在备份“{$remark}”数据库。请选择模式：" . PHP_EOL . PHP_EOL;
	// 输入错误次数（最大3次）
	$err_time = 0;
START:
	echo '1. 初始化备份（包含系统表结构+数据、应用表结构）' . PHP_EOL;
	echo '2. 全量备份（包含所有表结构+数据）' . PHP_EOL;
	// 交互，获取用户输入（选择备份模式）
	$act = fgets(STDIN);
	$act = (int)trim($act);
	switch($act)
	{
		case 1:
			$act = '_INIT';
			$act_name = '初始化备份';
			break;
		case 2:
			$act = '_BACKUP';
			$act_name = '全量备份';
			break;
		default:
			if($err_time < 2)
			{
				echo PHP_EOL . '未知的操作模式，请重新选择：' . PHP_EOL . PHP_EOL;
				$err_time++;
				goto START;
			}
			else
			{
				echo PHP_EOL . '错误次数过多，退出备份' . PHP_EOL;
				exit;
			}
	}

	// 根据操作类型调用对应的处理函数
	$act();
	// 清理临时文件
	clearTmpFile();

?>
